extern "C" {
#include	<luajit.h>
#include	<lualib.h>
#include	<lauxlib.h>
}

#include	"../utils/utils.h"
#include	"../utils/md5.h"
#include	"../utils/b64.h"

#include	<algorithm>
#include	<random>

namespace LuaApi { namespace LuaUtils {
	static int Random(lua_State * pL) {
		std::random_device iRand;
		double nRet = iRand() * 1.0 / (iRand.max() - iRand.min());

		int nTop = lua_gettop(pL);
		if (nTop == 0) {
			lua_pushnumber(pL, (lua_Number)nRet);
		} else if (nTop == 1) {
			lua_pushnumber(pL, (lua_Number)(nRet * lua_tonumber(pL, 1)));
		} else {
			double nMin = lua_tonumber(pL, 1);
			double nMax = lua_tonumber(pL, 2);
			nRet = nRet * (std::abs(nMax - nMin)) + std::min(nMin, nMax);
			lua_pushnumber(pL, (lua_Number)nRet);
		}

		return 1;
	}

	static int Tick(lua_State * pL) {
		lua_pushnumber(pL, ::Tick());
		return 1;
	}

	static int MetaIndex(lua_State * pL) {
		lua_getfield(pL, 1, "super");	// tb, key, tb.super
		lua_pushvalue(pL, 2);			// tb, key, tb.super, key
		lua_rawget(pL, -2);				// tb, key, tb.super[key]
		return 1;
	}

	static int Inherit(lua_State * pL) {
		luaL_checktype(pL, 1, LUA_TTABLE);

		lua_newtable(pL);

		lua_pushvalue(pL, 1);
		lua_setfield(pL, -2, "super");

		lua_newtable(pL);
		lua_pushcfunction(pL, &MetaIndex);
		lua_setfield(pL, -2, "__index");
		lua_setmetatable(pL, -2);

		return 1;
	}

	static int Exists(lua_State * pL) {
		const char * pPath = lua_tostring(pL, 1);
		lua_pushboolean(pL, ::Exists(pPath));
		return 1;
	}

	static int Guid(lua_State * pL) {
		lua_pushstring(pL, CreateID().c_str());
		return 1;
	}

	static int MD5(lua_State * pL) {
		size_t nLen = 0;
		const char * pData = lua_tolstring(pL, -1, &nLen);

		unsigned char p[16] = { 0 };
		char pHex[64] = { 0 };
		
		MD5_CTX iCtx;
		MD5_Init(&iCtx);		
		MD5_Update(&iCtx, (void *)pData, (unsigned long)nLen);
		MD5_Final(p, &iCtx);

		snprintf(pHex, 64, "%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
			p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7],
			p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]);

		lua_pushstring(pL, pHex);
		return 1;
	}

	static int B64Encode(lua_State * pL) {
		size_t nLen = 0;
		const char * pData = lua_tolstring(pL, -1, &nLen);

		base64_encodestate iState;
		base64_init_encodestate(&iState);

		char * pOut = new char[nLen * 2 + 3];
		int nSize = base64_encode_block(pData, (int)nLen, pOut, &iState);
		nSize += base64_encode_blockend(pOut + nSize, &iState);

		lua_pushlstring(pL, pOut, nSize);
		return 1;
	}

	static int B64Decode(lua_State * pL) {
		size_t nLen = 0;
		const char * pData = lua_tolstring(pL, -1, &nLen);

		base64_decodestate iState;
		base64_init_decodestate(&iState);

		char * pOut = new char[nLen];
		int nSize = base64_decode_block(pData, (const int)nLen, pOut, &iState);
		
		lua_pushlstring(pL, pOut, nSize);
		return 1;
	}
} }

void RegisterApi_Utils(lua_State * pL) {
	lua_pushcfunction(pL, &LuaApi::LuaUtils::MD5);
	lua_setglobal(pL, "md5");

	lua_pushcfunction(pL, &LuaApi::LuaUtils::Random);
	lua_setglobal(pL, "random");

	lua_pushcfunction(pL, &LuaApi::LuaUtils::Tick);
	lua_setglobal(pL, "tick");

	lua_pushcfunction(pL, &LuaApi::LuaUtils::Inherit);
	lua_setglobal(pL, "inherit");

	lua_pushcfunction(pL, &LuaApi::LuaUtils::Exists);
	lua_setglobal(pL, "exists");

	lua_pushcfunction(pL, &LuaApi::LuaUtils::Guid);
	lua_setglobal(pL, "uuid");

	lua_newtable(pL);
	lua_pushcfunction(pL, &LuaApi::LuaUtils::B64Encode);
	lua_setfield(pL, -2, "encode");
	lua_pushcfunction(pL, &LuaApi::LuaUtils::B64Decode);
	lua_setfield(pL, -2, "decode");
	lua_setglobal(pL, "b64");
}